/**
 * Copyright (c) 2010 Jesco Freund.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of Backtory nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id$
 */

#include "config.h"

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>

#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif


#include "backtoryd.h"
#include "message.h"
#include "backtory/error.h"


void show_usage()
{
	printf
	(
		PACKAGE_NAME"d v"PACKAGE_VERSION" ("__DATE__ " " __TIME__ ")\n\n"
		"usage:\n"
		" -h             show this help and exit\n"
		" -v             show version information and exit\n"
		" -4             force using IPv4 only\n"
		" -6             force using IPv6 only\n"
		" -D             don't go to background (default: go to background)\n"
		" -i <address>   listen on specified address\n"
		" -p <port>      listen on specified port\n"
		" -u <user>      run as user <user>\n"
		" -g <group>     run as group <group>\n"
		" -f <pidfile>   write daemon PID to the specified file\n"
		" -l <file>      log to specified file instead of syslog or terminal\n"
		"\n"
		"Please report any issues like bugs etc. at http://code.google.com/p/backtory\n"
	);
}


void show_version()
{
	printf(PACKAGE_NAME"d version "PACKAGE_VERSION" ("__DATE__ " " __TIME__ ")\n");
}


int main(int argc, char **argv)
{
	int ch, res, cstat, skip_daemon = 0;
	char *pidfile = NULL;
	char *logfile = NULL;
	char cwd[1024] = { '\0' };
	char ebuf[BACKTORY_EBUFSIZE] = { '\0' };
	msg_ctx_t prectx, ctx;
	by_nwctx_t nwctx;
	pid_t pid;
	
	res = msg_ctx_create(&prectx);
	if (0 != res)
	{
		by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
		fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
		exit(EXIT_FAILURE);
	}
	
	res = msg_ctx_create(&ctx);
	if (0 != res)
	{
		by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
		fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
		exit(EXIT_FAILURE);
	}
	
	res = msg_init_term(&prectx);
	if (0 != res)
	{
		by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
		fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
		exit(EXIT_FAILURE);
	}
	
	/* initialize network context */
	nwctx.family = AF_UNSPEC;
	nwctx.node = NULL;
	nwctx.port = (char *)malloc(strlen(BACKTORY_PORT) + 1);
	if (NULL == nwctx.port)
	{
		by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
		msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
		exit(EXIT_FAILURE);
	}
	strncpy(nwctx.port, BACKTORY_PORT, strlen(BACKTORY_PORT) + 1);
	nwctx.user = NULL;
	nwctx.group = NULL;
	
	while ((ch = getopt(argc, argv, "46hsvDf:i:l:p:u:g:")) != -1)
	{
		switch (ch)
		{
			case 'h':
				show_usage();
				exit(EXIT_SUCCESS);
				break;
			case 'v':
				show_version();
				exit(EXIT_SUCCESS);
				break;
			case '4':
				nwctx.family = AF_INET;
				break;
			case '6':
				nwctx.family = AF_INET6;
				break;
			case 'D':
				skip_daemon = 1;
				break;
			case 'f':
				if ((NULL != optarg) && (strlen(optarg) > 0) && (optarg[0] != '/'))
				{
					memset(cwd, (int)'\0', 1024);
					getcwd(cwd, 1024);
					pidfile = (char *)malloc(strlen(cwd) + strlen(optarg) + 2);
					strncpy(pidfile, cwd, strlen(cwd) + 1);
					strncat(pidfile, "/", 2);
					strncat(pidfile, optarg, strlen(optarg) + 1);
				}
				else
				{
					pidfile = optarg;
				}
				break;
			case 'g':
				if ((NULL != optarg) && (strlen(optarg) > 0))
				{
					nwctx.group = (char *)malloc(strlen(optarg)+1);
					if (NULL == nwctx.group)
					{
						by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
						msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
						exit(EXIT_FAILURE);
					}
					strncpy(nwctx.group, optarg, strlen(optarg)+1);
				}
				else
				{
					fprintf(stderr, "Expecting parameter for argument -g\n");
					exit(EXIT_FAILURE);
				}
				break;
			case 'i':
				if ((NULL != optarg) && (strlen(optarg) > 0))
				{
					nwctx.node = (char *)malloc(strlen(optarg)+1);
					if (NULL == nwctx.node)
					{
						by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
						msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
						exit(EXIT_FAILURE);
					}
					strncpy(nwctx.node, optarg, strlen(optarg)+1);
				}
				else
				{
					fprintf(stderr, "Expecting parameter for argument -i\n");
					exit(EXIT_FAILURE);
				}
				break;
			case 'l':
				if ((NULL != optarg) && (strlen(optarg) > 0) && (optarg[0] != '/'))
				{
					memset(cwd, (int)'\0', 1024);
					getcwd(cwd, 1024);
					logfile = (char *)malloc(strlen(cwd) + strlen(optarg) + 2);
					strncpy(logfile, cwd, strlen(cwd) + 1);
					strncat(logfile, "/", 2);
					strncat(logfile, optarg, strlen(optarg) + 1);
				}
				else
				{
					logfile = optarg;
				}
				res = msg_init_file(&ctx, logfile);
				if (0 != res)
				{
					by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
					fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
					exit(EXIT_FAILURE);
				}
				break;
			case 'p':
				if ((NULL != optarg) && (strlen(optarg) > 0))
				{
					nwctx.port = (char *)realloc(nwctx.port, strlen(optarg)+1);
					if (NULL == nwctx.port)
					{
						by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
						msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
						exit(EXIT_FAILURE);
					}
					strncpy(nwctx.port, optarg, strlen(optarg)+1);
				}
				else
				{
					fprintf(stderr, "Expecting parameter for argument -p\n");
					exit(EXIT_FAILURE);
				}
				break;
			case 's':
				/* option to force syslog initialization
				will remain undocumented since using syslog is the default behaviour
				if ctx had not been initialized otherwise (using a log file)
					*/
				res = msg_init_syslog(&ctx, PACKAGE_NAME, LOG_PID, LOG_DAEMON);
				if (0 != res)
				{
					by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
					fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
					exit(EXIT_FAILURE);
				}
				break;
			case 'u':
				if ((NULL != optarg) && (strlen(optarg) > 0))
				{
					nwctx.user = (char *)malloc(strlen(optarg)+1);
					if (NULL == nwctx.user)
					{
						by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
						msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
						exit(EXIT_FAILURE);
					}
					strncpy(nwctx.user, optarg, strlen(optarg)+1);
				}
				else
				{
					fprintf(stderr, "Expecting parameter for argument -u\n");
					exit(EXIT_FAILURE);
				}
				break;
			case '?':
			default:
				exit(EXIT_FAILURE);
		}
	}
	argc -= optind;
	argv += optind;
	
	if ((ctx.status != INITIALIZED) && (!skip_daemon))
	{
#ifdef HAVE_SYSLOG_H
		res = msg_init_syslog(&ctx, PACKAGE_NAME, LOG_PID, LOG_DAEMON);
		if (0 != res)
		{
			by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
			fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
			exit(EXIT_FAILURE);
		}
#else
		msg_log(&prectx, LOG_ERR, "For running in daemon mode, logging to a log file is mandatory.\nPlease use the -l option to specify a file to log to.\n");
		exit(EXIT_FAILURE);
#endif /* HAVE_SYSLOG_H */
	}
	else if ((ctx.status != INITIALIZED) && (skip_daemon))
	{
		res = msg_init_term(&ctx);
		if (0 != res)
		{
			by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
			fprintf(stderr, "Error %d in main: %s\n", res, ebuf);
			exit(EXIT_FAILURE);
		}
	}
	
	if (!skip_daemon)
	{
		if (NULL == pidfile)
		{
			pidfile = (char *)malloc(strlen(BACKTORY_PIDFILE) + 1);
			if (NULL == pidfile)
			{
				by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
				msg_log(&prectx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
				exit(EXIT_FAILURE);
			}
			strncpy(pidfile, BACKTORY_PIDFILE, strlen(BACKTORY_PIDFILE) + 1);
		}
		daemonize(&prectx, &ctx, pidfile);
	}
	
	res = msg_close(&prectx);
	if (0 != res)
	{
		by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
		msg_log(&ctx, LOG_WARNING, "Error %d in main: %s\n", res, ebuf);
	}
	
	res = msg_ctx_destroy(&prectx);
	if (0 != res)
	{
		by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
		msg_log(&ctx, LOG_WARNING, "Error %d in main: %s\n", res, ebuf);
	}
	
	/* We need pipes for two-way communication */
	by_pipe_t cgate;
	if ((pipe(cgate.pc) < 0) || (pipe(cgate.cp) < 0))
	{
		by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
		msg_log(&ctx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
		exit(EXIT_FAILURE);
	}
	
	/* fork a child process which will play the network part */
	switch (pid = fork())
	{
		case -1:
			by_strerror_r(errno, ebuf, BACKTORY_EBUFSIZE);
			msg_log(&ctx, LOG_ERR, "Error %d in main: %s\n", errno, ebuf);
			exit(EXIT_FAILURE);
			break;
		case 0:
			/* child process */
			res = nwc_main(&ctx, &cgate, &nwctx);
			if (res)
			{
				by_strerror_r(res, ebuf, BACKTORY_EBUFSIZE);
				msg_log(&ctx, LOG_ERR, "Error %d in main: %s\n", res, ebuf);
				exit(EXIT_FAILURE);
			}
			exit(EXIT_SUCCESS);
			break;
		default:
			/* parent process */
			close(cgate.cp[1]);
			close(cgate.pc[0]);
			
			/* TODO
			 * This needs implementation (signal stuff and all...)
			 */
			waitpid(pid, &cstat, 0);
	}
	
	if (NULL != pidfile)
		unlink(pidfile);
	
	return EXIT_SUCCESS;
}
